package com.atguigu.tingshu.search.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Buckets;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.base.BaseEntity;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.Repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.Repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumFeignClient albumFeignClient;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private AlbumInfoIndexRepository albumInfoIndexRepository;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    @Override
    public void upperAlbum(Long albumId) {
        //创建返回index对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        //执行任务A
        CompletableFuture<AlbumInfo> completableFutureA = CompletableFuture.supplyAsync(() -> {
            //1.调用专辑服务查询专辑信息
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
            //1.1判断是否为空
            Assert.notNull(albumInfo, "该专辑下无内容");
            //1.2 封装AlbumInfoIndex对象专辑信息
            BeanUtil.copyProperties(albumInfo, albumInfoIndex);
            //1.3 封装AlbumInfoIndex对象标签信息
            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
            if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
                List<AttributeValueIndex> attributeValueIndexLists = albumAttributeValueVoList.stream()
                        .map(AlbumAttributeValue -> BeanUtil.copyProperties(AlbumAttributeValue, AttributeValueIndex.class))
                        .collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexLists);
            }
            return albumInfo;
        }, threadPoolExecutor);


        //1.4 调用专辑服务，获取三级分类的ID,封装进albumInfoIndex 分析：该任务需要获取专辑的返回结果才能开始执行，需要同步任务调用
        CompletableFuture<Void> categoryCompletableFuture = completableFutureA.thenAcceptAsync(albumInfo -> {
            BaseCategoryView baseCategoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData();
            Assert.notNull(baseCategoryView, "分类{}信息为空", albumInfo.getCategory3Id());
            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
        }, threadPoolExecutor);

        //2.调用用户服务查询用户信息 分析：用户服务需要获取专辑中的用户id才执行，与专辑任务同步执行，与获取三级分类任务异步执行
        CompletableFuture<Void> userInfoCompletableFuture = completableFutureA.thenAcceptAsync(albumInfo -> {
            UserInfoVo userInfo = userFeignClient.getUserInfoByUserId(albumInfo.getUserId()).getData();
            Assert.notNull(userInfo, "该用户信息为空");
            //3.封装AlbumInfoIndex对象用户信息
            albumInfoIndex.setAnnouncerName(userInfo.getNickname());
        }, threadPoolExecutor);

        //为了方便检索，手动设置统计数值 分析：统计数值的查询不依赖专辑的信息，可以与专辑任务异步进行
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            int num1 = RandomUtil.randomInt(100, 2000);
            int num2 = RandomUtil.randomInt(100, 1500);
            int num3 = RandomUtil.randomInt(100, 1000);
            int num4 = RandomUtil.randomInt(100, 1000);
            albumInfoIndex.setBuyStatNum(num4);
            albumInfoIndex.setCommentStatNum(num1);
            albumInfoIndex.setPlayStatNum(num3);
            albumInfoIndex.setSubscribeStatNum(num2);
            //4.基于统计值计算当前文档热度 统计量*动态权重
            BigDecimal bigDecimal1 = new BigDecimal(0.1).add(new BigDecimal(num1));
            BigDecimal bigDecimal2 = new BigDecimal(0.2).add(new BigDecimal(num2));
            BigDecimal bigDecimal3 = new BigDecimal(0.3).add(new BigDecimal(num3));
            BigDecimal bigDecimal4 = new BigDecimal(0.4).add(new BigDecimal(num4));
            BigDecimal hotScore = bigDecimal1.add(bigDecimal2).add(bigDecimal3).add(bigDecimal4);
            albumInfoIndex.setHotScore(hotScore.doubleValue());
        }, threadPoolExecutor);
        //结合四个异步任务
        CompletableFuture.allOf(
                        completableFutureA,
                        categoryCompletableFuture,
                        userInfoCompletableFuture,
                        completableFuture
                )
                .join();


        //5.调用持久层文档存入索引库
        albumInfoIndexRepository.save(albumInfoIndex);

        //将专辑标题内容作为原始题词记录存储到提词库
        //创建题词对象
        SuggestIndex suggestIndex = new SuggestIndex();
        //封装题词记录主键和专辑文档主键一样
        suggestIndex.setId(albumInfoIndex.getId().toString());
        //封装题词原始内容 给用户展示的题词内容（专辑标题）
        suggestIndex.setTitle(albumInfoIndex.getAlbumTitle());
        //用于题词字段:汉字题词
        suggestIndex.setKeywordPinyin(new Completion(new String[]{PinyinUtil.getPinyin(albumInfoIndex.getAlbumTitle())}));
        suggestIndex.setKeyword(new Completion(new String[]{suggestIndex.getTitle()}));
        suggestIndex.setKeywordSequence(new Completion(new String[]{PinyinUtil.getFirstLetter("albumTitle","")}));
        //保存到检索库里
        suggestIndexRepository.save(suggestIndex);
    }

    @Override
    public void lowerAlbum(Long albumId) {
        albumInfoIndexRepository.deleteById(albumId);
        //
    }

    /**
     * 站内检索，支持关键字、分类、标签条件分页检索，结果高亮
     * todo 根据关键字进行检索专辑
     *
     * @param albumIndexQuery
     * @return
     */
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {

        try {
            //逻辑
            //远程调用专辑服务，根据专辑id 查询所有的专辑的信息，然后根据传回来的关键字进行，es查询
            //创建检索请求对象
            SearchRequest searchRequest = this.buildDSL(albumIndexQuery);
            //调用执行检索
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
            //获取返回结果
            return this.parseResult(searchResponse, albumIndexQuery);
        } catch (IOException e) {
            log.error("\"[搜索服务]站内检索异常：{}\", e");
            throw new RuntimeException(e);
        }
    }

    /**
     * 基于入参查询条件构建完整检索请求对象
     *
     * @param albumIndexQuery 查询条件
     * @return 检索请求对象
     */

    private static final String INDEX_NAME = "albuminfo";

    @Override
    public SearchRequest buildDSL(AlbumIndexQuery albumIndexQuery) {
        //创建reques请求对象


        //1.创建检索请求构建器对象-封装检索索引库
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(INDEX_NAME);
        //2.设置查询条件对应请求体参数中"query" 封装关键字、分类过滤、标签过滤
        String keyword = albumIndexQuery.getKeyword();
        //2.1 创建最外层组合多条件查询对象-封装所有查询条件
        BoolQuery.Builder allBoolQueryBuilder = new BoolQuery.Builder();
        //2.2 处理关键字检索 关键字全文检索专辑标题，简介。等值精确查询作者名称
        if (StringUtils.isNotBlank(keyword)) {
            allBoolQueryBuilder.must(
                    m -> m.bool(
                            b -> b.should(s -> s.match(ma -> ma.field("albumTitle").query(keyword)))
                                    .should(s -> s.match(ma -> ma.field("albumIntro").query(keyword)))
                                    .should(s -> s.term(t -> t.field("announcerName").value(keyword)))
                    )
            );
        }
        //2.3 处理分类过滤
        //2.3.1 判断1级分类是否提交，设置1级分类过滤
        if (albumIndexQuery.getCategory1Id() != null) {
            allBoolQueryBuilder.filter(f -> f.term(t -> t.field("category1Id").value(albumIndexQuery.getCategory1Id())));
        }
        //2.3.2 判断2级分类是否提交，设置2级分类过滤
        if (albumIndexQuery.getCategory2Id() != null) {
            allBoolQueryBuilder.filter(f -> f.term(t -> t.field("category2Id").value(albumIndexQuery.getCategory2Id())));
        }
        //2.3.3 判断3级分类是否提交，设置3级分类过滤
        if (albumIndexQuery.getCategory3Id() != null) {
            allBoolQueryBuilder.filter(f -> f.term(t -> t.field("category3Id").value(albumIndexQuery.getCategory3Id())));
        }
        //2.4 处理标签过滤
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (CollectionUtil.isNotEmpty(attributeList)) {
            //2.4.1 前端可能提交多个标签过滤条件 提交标签形式  标签id：标签值Id
            for (String s : attributeList) {
                //每循环一次，封装标签Nested查询
                String[] split = s.split(":");
                allBoolQueryBuilder.filter(f -> f.nested(n ->
                        n.path("attributeValueIndexList")
                                .query(q -> q.bool(
                                        b -> b.must(m -> m.term(t -> t.field("attributeValueIndexList.attributeId").value(split[0])))
                                                .must(m -> m.term(t -> t.field("attributeValueIndexList.valueId").value(split[1])))
                                ))
                ));
            }
        }
        builder.query(allBoolQueryBuilder.build()._toQuery());
        //3.设置分页对应请求体参数中"from" "size" 封装起始位置，页大小
        Integer pageSize = albumIndexQuery.getPageSize();
        int from = (albumIndexQuery.getPageNo() - 1) * pageSize;
        builder.from(from);
        builder.size(pageSize);
        //4.设置排序对应请求体参数中"sort" 前段提交参数=排序字段:排序方式
        String order = albumIndexQuery.getOrder();
        if (StringUtils.isNotBlank(order)) {
            //4.1 对排序字符按照:进行切割
            String[] split = order.split(":");
            //4.2 得到排序字段及排序方式 综合排序[1:desc] 播放量[2:desc] 发布时间[3:desc]
            String orderFiled = "";
            switch (split[0]) {
                case "1":
                    orderFiled = "hotScore";
                    break;
                case "2":
                    orderFiled = "playStatNum";
                    break;
                case "3":
                    orderFiled = "createTime";
                    break;
            }
            String finalOrderFiled = orderFiled;
            builder.sort(s -> s.field(f -> f.field(finalOrderFiled).order("asc".equals(split[1]) ? SortOrder.Asc : SortOrder.Desc)));
        }
        //5.设置高亮显示对应请求体参数中"highlight"
        if (StringUtils.isNotBlank(keyword)) {
            builder.highlight(h -> h.fields("albumTitle", f -> f.preTags("<font style='color:red'>").postTags("</font>")));
        }
        //6.设置查询及返回字段列表
        builder.source(s -> s.filter(f -> f.excludes("category1Id", "category2Id", "category3Id", "hotScore", "attributeValueIndexList.attributeId", "attributeValueIndexList.valueId")));
        //7.基于构建器对象得到检索请求对象
        return builder.build();
    }

    /**
     * 解析ES响应结果，将ES响应JSON转为AlbumSearchResponseVo对象
     *
     * @param searchResponse  ES响应结果
     * @param albumIndexQuery
     * @return
     */
    @Override
    public AlbumSearchResponseVo parseResult(SearchResponse<AlbumInfoIndex> searchResponse, AlbumIndexQuery albumIndexQuery) {

        AlbumSearchResponseVo vo = new AlbumSearchResponseVo();
        //1.封装分页相关信息 页码、页大小、总记录数、总页数
        //1.1 从入参中获取页码、页大小
        vo.setPageNo(albumIndexQuery.getPageNo());
        Integer pageSize = albumIndexQuery.getPageSize();
        vo.setPageSize(pageSize);
        //1.2 从ES检索结果中获取命中记录数
        long total = searchResponse.hits().total().value();
        //1.3 计算总页数
        long totalPages = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        vo.setTotalPages(totalPages);

        //2.封装检索到专辑列表数据（处理高亮）
        //2.1 获取命中hits集合，遍历得到Hit对象中source(AlbumInfoIndex) 转为 AlbumInfoIndexVo
        List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
        if (CollectionUtil.isNotEmpty(hits)) {
            List<AlbumInfoIndexVo> albumInfoIndexVoList = hits.stream().map(hit -> {
                //2.1.1 将得到AlbumInfoIndex类型转为AlbumInfoIndexVo
                AlbumInfoIndex albumInfoIndex = hit.source();
                AlbumInfoIndexVo albumInfoIndexVo = BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class);
                //2.1.2 处理高亮片段
                Map<String, List<String>> highlightMap = hit.highlight();
                if (CollectionUtil.isNotEmpty(highlightMap)) {
                    String albumTitleHighlight = highlightMap.get("albumTitle").get(0);
                    albumInfoIndexVo.setAlbumTitle(albumTitleHighlight);
                }
                return albumInfoIndexVo;
            }).collect(Collectors.toList());
            vo.setList(albumInfoIndexVoList);
        }
        return vo;
    }

    @Override
    public List<Map<String, Object>> getTopCategoryAlbumList(Long category1Id) {
        try {
            //根据一级分类id查询置顶的10个三级分类信息
            List<BaseCategory3> Category3Data = albumFeignClient.findTopBaseCategory3(category1Id).getData();
            //收集三级id
            List<Long> Category3IdList = Category3Data.stream().map(BaseEntity::getId).collect(Collectors.toList());
            //1.1.1 检索需要将三级分类ID集合泛型转为FieldValue
            List<FieldValue> catGory3Idlist = Category3IdList.stream().map(Category3Id -> FieldValue.of(Category3Id)).collect(Collectors.toList());
            //掉用ES进行检索
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(s -> s.query(q -> q.terms(t -> t.field("category3Id").terms(t1 -> t1.value(catGory3Idlist)))).size(0)
                    //聚合函数
                    .aggregations("category3Agg", a -> a.terms(t -> t.field("category3Id").size(20)).aggregations("Top6", al -> al.topHits(t -> t.size(6).sort(sort -> sort.field(f -> f.field("hotScore").order(SortOrder.Desc)))))), AlbumInfoIndex.class);
            //3.解析ES聚合结果
            Map<String, Aggregate> aggregateMap = searchResponse.aggregations();
            Map<Long, BaseCategory3> baseCategory3Map = Category3Data.stream().collect(Collectors.toMap(BaseCategory3::getId, baseCategory3 -> baseCategory3));


            //3.1 获取到三级分类ID聚合桶集合
            Aggregate category3Agg = aggregateMap.get("category3Agg");
            LongTermsAggregate category3lterms = category3Agg.lterms();
            List<LongTermsBucket> category3Buckets = category3lterms.buckets().array();
            //3.2 遍历三级分类Id聚合桶 没遍历一个得到一个热门专辑
            List<Map<String, Object>> list = category3Buckets.stream().map(category3Bucket -> {
                //获取三级id
                long category3Id = category3Bucket.key();
                //遍历当前三级分类聚合中得到的子聚合得到置顶的热门专辑
                Aggregate top6 = category3Bucket.aggregations().get("Top6");
                List<AlbumInfoIndex> hotAlbumInfoList = top6.topHits().hits().hits().stream().map(hitt -> {
                    String string = hitt.source().toJson().toString();
                    AlbumInfoIndex albumInfoIndex = JSON.parseObject(string, AlbumInfoIndex.class);
                    return albumInfoIndex;
                }).collect(Collectors.toList());
                Map<String, Object> map = new HashMap<>();
                map.put("baseCategory3", baseCategory3Map.get(category3Id));
                map.put("list", hotAlbumInfoList);
                return map;
            }).collect(Collectors.toList());
            return list;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
